﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Myotragus.Data.Util
{
	public static class ExecutionHelper
	{
		public static bool NoErrors(Action actual)
		{
			return ControlledExecution(actual) == null;
		}

		public static Exception ControlledExecution(Action actual)
		{
			try
			{
				actual();
				return null;
			}
			catch (Exception ex)
			{
				return ex;
			}
		}

		public static TResult ControlledExecution<TResult>(Func<TResult> actual, TResult onError)
		{
			try
			{
				return actual();
			}
			catch
			{
				return onError;
			}
		}

		public static TException CreateException<TException>(Exception inner, string message, params object[] args)
			where TException : Exception
		{
			return (TException)CreateException(typeof(TException), inner, message, args);
		}

		public static TException CreateException<TException>(string message, params object[] args)
			where TException : Exception
		{
			return (TException)CreateException(typeof(TException), null, message, args);
		}

		public static TException CreateException<TException>()
			where TException : Exception
		{
			return (TException)CreateException(typeof(TException), null, string.Empty);
		}

		public static TException CreateException<TException>(Exception inner)
			where TException : Exception
		{
			return (TException)CreateException(typeof(TException), inner, string.Empty);
		}

		private static readonly Type[] ParamsMessageAndInnerException = new[] { typeof(string), typeof(Exception) };
		private static readonly Type[] ParamsMessage = new[] { typeof(string) };
		private static readonly Type[] ParamsNone = new Type[0];
		private static readonly IEnumerable<Type[]> PreferredCtorParams = new[] {
			ParamsMessageAndInnerException, ParamsMessage, ParamsNone};

		public static Exception CreateException(Type type)
		{
			return CreateException(type, null, string.Empty);
		}

		public static Exception CreateException(Type type, string message, params object[] args)
		{
			return CreateException(type, null, message, args);
		}

		public static Exception CreateException(Type type, Exception inner)
		{
			return CreateException(type, inner, string.Empty);
		}

		public static Exception CreateException(Type type, Exception inner, string message, params object[] args)
		{
			if (args != null && args.Any())
				message = string.Format(message, args);

			var ctorParamsTemplate = new object[] { message, inner };

			foreach (var paramTypes in PreferredCtorParams)
			{
				var ctor = type.GetConstructor(paramTypes);
				if (ctor == null) continue;

				var ctorParams = ctorParamsTemplate.Take(paramTypes.Count()).ToArray();
				return (Exception)ctor.Invoke(ctorParams);
			}

			throw new Exception("Cannnot create exception instance");
		}
	}
}